﻿using System.Collections.Concurrent;

namespace Sgr.OmniMetrics.Services
{
    /// <summary>
    /// 基于内存存储的默认指标服务实现
    /// <para>该实现使用内存字典存储指标数据，适用于开发测试或小规模使用场景</para>
    /// <para>注意：数据会在应用程序重启后丢失</para>
    /// </summary>
    public class DefaultOmniMetricService : IOmniMetricService
    {
        /// <summary>
        /// 指标数据存储
        /// <para>使用三层嵌套字典结构：</para>
        /// <para>第一层：指标键(MetricKey) -> 粒度存储</para>
        /// <para>第二层：统计粒度(Granularity) -> 时间存储</para>
        /// <para>第三层：时间点(TimeValue) -> 指标对象</para>
        /// </summary>
        private readonly ConcurrentDictionary<string, ConcurrentDictionary<Granularity, ConcurrentDictionary<DateTime, object>>> _metricsStore = new();

        /// <summary>
        /// 保存单个指标数据
        /// </summary>
        /// <typeparam name="T">指标扩展数据类型</typeparam>
        /// <param name="metric">待保存的指标数据</param>
        /// <param name="cancellationToken">取消令牌</param>
        /// <returns>保存成功的指标数据</returns>
        /// <exception cref="ArgumentNullException">当 metric 为 null 时抛出</exception>
        public Task<OmniMetric<T>> SaveMetricAsync<T>(OmniMetric<T> metric, CancellationToken cancellationToken = default) where T : class
        {
            if (metric == null) throw new ArgumentNullException(nameof(metric));

            // 确保时间对齐到统计粒度
            metric.AlignTimeValue();

            // 获取或创建指标键的粒度存储字典
            var granularityStore = _metricsStore.GetOrAdd(metric.MetricKey, _ =>
                new ConcurrentDictionary<Granularity, ConcurrentDictionary<DateTime, object>>());

            // 获取或创建粒度的时间存储字典
            var timeStore = granularityStore.GetOrAdd(metric.TimeGranularity, _ =>
                new ConcurrentDictionary<DateTime, object>());

            // 在时间点存储指标数据
            timeStore[metric.TimeValue] = metric;

            return Task.FromResult(metric);
        }

        /// <summary>
        /// 批量保存指标数据
        /// </summary>
        /// <typeparam name="T">指标扩展数据类型</typeparam>
        /// <param name="metrics">待保存的指标数据集合</param>
        /// <param name="cancellationToken">取消令牌</param>
        /// <exception cref="ArgumentNullException">当 metrics 为 null 时抛出</exception>
        public async Task SaveMetricsAsync<T>(IEnumerable<OmniMetric<T>> metrics, CancellationToken cancellationToken = default) where T : class
        {
            if (metrics == null) throw new ArgumentNullException(nameof(metrics));

            foreach (var metric in metrics)
            {
                await SaveMetricAsync(metric, cancellationToken);
            }
        }

        /// <summary>
        /// 获取指定指标的最新数据
        /// </summary>
        /// <typeparam name="T">指标扩展数据类型</typeparam>
        /// <param name="metricKey">指标键</param>
        /// <param name="granularity">统计粒度</param>
        /// <param name="cancellationToken">取消令牌</param>
        /// <returns>最新的指标数据，如果不存在则返回 null</returns>
        /// <exception cref="ArgumentNullException">当 metricKey 为 null 或空时抛出</exception>
        public Task<OmniMetric<T>?> GetLatestMetricAsync<T>(string metricKey, Granularity granularity, CancellationToken cancellationToken = default) where T : class
        {
            if (string.IsNullOrEmpty(metricKey)) throw new ArgumentNullException(nameof(metricKey));

            if (_metricsStore.TryGetValue(metricKey, out var granularityStore) &&
                granularityStore.TryGetValue(granularity, out var timeStore))
            {
                var latestTime = timeStore.Keys.MaxBy(t => t);
                if (latestTime != default && timeStore.TryGetValue(latestTime, out var metricObj))
                {
                    return Task.FromResult(metricObj as OmniMetric<T>);
                }
            }

            return Task.FromResult<OmniMetric<T>?>(null);
        }

        /// <summary>
        /// 获取指定时间范围内的指标数据列表
        /// </summary>
        /// <typeparam name="T">指标扩展数据类型</typeparam>
        /// <param name="metricKey">指标键</param>
        /// <param name="granularity">统计粒度</param>
        /// <param name="startTime">起始时间</param>
        /// <param name="endTime">结束时间</param>
        /// <param name="cancellationToken">取消令牌</param>
        /// <returns>符合条件的指标数据列表</returns>
        /// <exception cref="ArgumentNullException">当 metricKey 为 null 或空时抛出</exception>
        public Task<IReadOnlyList<OmniMetric<T>>> GetMetricsAsync<T>(
            string metricKey,
            Granularity granularity,
            DateTime startTime,
            DateTime endTime,
            CancellationToken cancellationToken = default) where T : class
        {
            if (string.IsNullOrEmpty(metricKey)) throw new ArgumentNullException(nameof(metricKey));

            var results = new List<OmniMetric<T>>();

            if (_metricsStore.TryGetValue(metricKey, out var granularityStore) &&
                granularityStore.TryGetValue(granularity, out var timeStore))
            {
                var metrics = timeStore
                    .Where(kvp => kvp.Key >= startTime && kvp.Key <= endTime)
                    .Select(kvp => kvp.Value as OmniMetric<T>)
                    .Where(m => m != null)
                    .OrderBy(m => m!.TimeValue)
                    .ToList();

                results.AddRange(metrics!);
            }

            return Task.FromResult<IReadOnlyList<OmniMetric<T>>>(results);
        }

        /// <summary>
        /// 删除指定时间范围内的指标数据
        /// </summary>
        /// <param name="metricKey">指标键</param>
        /// <param name="granularity">统计粒度</param>
        /// <param name="startTime">起始时间</param>
        /// <param name="endTime">结束时间</param>
        /// <param name="cancellationToken">取消令牌</param>
        /// <returns>表示异步操作的任务</returns>
        /// <exception cref="ArgumentNullException">当 metricKey 为 null 或空时抛出</exception>
        /// <remarks>
        /// 该方法会同时清理空的存储容器：
        /// 1. 如果时间存储为空，则移除对应的粒度存储
        /// 2. 如果粒度存储为空，则移除对应的指标键存储
        /// </remarks>
        public Task DeleteMetricsAsync(
            string metricKey,
            Granularity granularity,
            DateTime startTime,
            DateTime endTime,
            CancellationToken cancellationToken = default)
        {
            if (string.IsNullOrEmpty(metricKey)) throw new ArgumentNullException(nameof(metricKey));

            if (_metricsStore.TryGetValue(metricKey, out var granularityStore) &&
                granularityStore.TryGetValue(granularity, out var timeStore))
            {
                var keysToRemove = timeStore.Keys
                    .Where(time => time >= startTime && time <= endTime)
                    .ToList();

                foreach (var key in keysToRemove)
                {
                    timeStore.TryRemove(key, out _);
                }

                // 清理空的存储容器
                if (timeStore.IsEmpty)
                {
                    granularityStore.TryRemove(granularity, out _);
                    if (granularityStore.IsEmpty)
                    {
                        _metricsStore.TryRemove(metricKey, out _);
                    }
                }
            }

            return Task.CompletedTask;
        }
    }
}